--- 模块功能：根据基站信息查询经纬度
-- @module lbsLoc
-- @author openLuat
-- @license MIT
-- @copyright openLuat
-- @release 2018.03.25
require "socket"
require "utils"
require "common"
require "misc"
module(..., package.seeall)

local function enCellInfo(s)
    local ret, t, cntrssi = "", {}
    log.info("lbsLoc.enCellInfo", s)
    for mcc, mnc, lac, ci, rssi in string.gmatch(s, "(%d+)%.(%d+)%.(%d+)%.(%d+)%.(%d+);") do
        mcc, mnc, lac, ci, rssi = tonumber(mcc), tonumber(mnc), tonumber(lac), tonumber(ci), (tonumber(rssi) > 31) and 31 or tonumber(rssi)
        local handle = nil
        for k, v in pairs(t) do
            if v.lac == lac and v.mcc == mcc and v.mnc == mnc then
                if #v.rssici < 8 then
                    table.insert(v.rssici, {rssi = rssi, ci = ci})
                end
                handle = true
                break
            end
        end
        if not handle then
            table.insert(t, {mcc = mcc, mnc = mnc, lac = lac, rssici = {{rssi = rssi, ci = ci}}})
        end
    end
    for k, v in pairs(t) do
        ret = ret .. pack.pack(">HHb", v.lac, v.mcc, v.mnc)
        for m, n in pairs(v.rssici) do
            cntrssi = bit.bor(bit.lshift(((m == 1) and (#v.rssici - 1) or 0), 5), n.rssi)
            ret = ret .. pack.pack(sys.is8955 and ">bH" or ">bi", cntrssi, n.ci)
        end
    end

    return string.char(#t) .. ret
end

local function enWifiInfo(tWifi)
    local ret, cnt = "", 0
    if tWifi then
        for k, v in pairs(tWifi) do
            log.info("lbsLoc.enWifiInfo", k, v)
            ret = ret .. pack.pack("Ab", (k:gsub(":", "")):fromHex(), (v < 0) and (v + 255) or v)
            cnt = cnt + 1
        end
    end
    return (tWifi and string.char(cnt) or "") .. ret
end

local function trans(str)
    local s = str
    if str:len() < 10 then
        s = str .. string.rep("0", 10 - str:len())
    end
    return s:sub(1, 3) .. "." .. s:sub(4, 10)
end

local function taskClient(cbFnc, reqAddr, timeout, productKey, host, port, reqTime, reqWifi)
    while not socket.isReady() do
        if not sys.waitUntil("IP_READY_IND", timeout) then
            sys.publish("LBSLOC_RESULT", false, false)
            if cbFnc then cbFnc(1) end
            return
        end
    end

    local retryCnt, sck = 0
    local reqStr = pack.pack("bAbAAAA",
        productKey:len(),
        productKey,
        (reqAddr and 2 or 0) + (reqTime and 4 or 0) + (sys.is8955 and 0 or 8) + (reqWifi and 16 or 0),
        -- (reqAddr and 2 or 0) + (reqTime and 4 or 0) + 8 + (reqWifi and 16 or 0),
        "",
        common.numToBcdNum(misc.getImei()),
        enCellInfo(net.getCellInfoExt()),
        enWifiInfo(reqWifi))
    log.info("reqStr", reqStr:toHex())
    while true do
        sck = socket.udp()
        if not sck then
            sys.publish("LBSLOC_RESULT", false, false)
            if cbFnc then cbFnc(6) end
            return
        end
        if sck:connect(host, port) then
            while true do
                if sck:send(reqStr) then
                    local result, data = sck:recv(5000)
                    if result then
                        sck:close()
                        log.info("lbcLoc receive", data:toHex())
                        if data:len() >= 11 and (data:byte(1) == 0 or data:byte(1) == 0xFF) then
                            sys.publish("LBSLOC_RESULT", trans(common.bcdNumToNum(data:sub(7, 11)))
                                , trans(common.bcdNumToNum(data:sub(2, 6))))
                            if cbFnc then
                                cbFnc(0, trans(common.bcdNumToNum(data:sub(2, 6))),
                                    trans(common.bcdNumToNum(data:sub(7, 11))),
                                    reqAddr and data:sub(13, 12 + data:byte(12)) or nil,
                                    data:sub(reqAddr and (13 + data:byte(12)) or 12, -1),
                                    (data:byte(1) == 0) and "LBS" or "WIFI")
                            end
                        else
                            log.warn("lbsLoc.query", "根据基站查询经纬度失败")
                            if data:byte(1) == 2 then
                                log.warn("lbsLoc.query", "main.lua中的PRODUCT_KEY和此设备在iot.openluat.com中所属项目的ProductKey必须一致，请去检查")
                            else
                                log.warn("lbsLoc.query", "基站数据库查询不到所有小区的位置信息")
                                log.warn("lbsLoc.query", "在trace中向上搜索encellinfo，然后在电脑浏览器中打开http://bs.openluat.com/，手动查找encellinfo后的所有小区位置")
                                log.warn("lbsLoc.query", "如果手动可以查到位置，则服务器存在BUG，直接向技术人员反映问题")
                                log.warn("lbsLoc.query", "如果手动无法查到位置，则基站数据库还没有收录当前设备的小区位置信息，向技术人员反馈，我们会尽快收录")
                            end
                            sys.publish("LBSLOC_RESULT", false, false)
                            if cbFnc then cbFnc(5) end
                        end
                        return
                    else
                        sck:close()
                        retryCnt = retryCnt + 1
                        if retryCnt >= 3 then
                            sys.publish("LBSLOC_RESULT", false, false)
                            if cbFnc then cbFnc(4) end
                            return
                        end
                        break
                    end
                else
                    sck:close()
                    retryCnt = retryCnt + 1
                    if retryCnt >= 3 then
                        sys.publish("LBSLOC_RESULT", false, false)
                        if cbFnc then cbFnc(3) end
                        return
                    end
                    break
                end
            end
        else
            sck:close()
            retryCnt = retryCnt + 1
            if retryCnt >= 3 then
                sys.publish("LBSLOC_RESULT", false, false)
                if cbFnc then cbFnc(2) end
                return
            end
        end
    end
end

--- 发送根据基站查询经纬度请求（仅支持中国区域的位置查询）
-- @function cbFnc，用户回调函数，回调函数的调用形式为：
--              cbFnc(result,lat,lng,addr,dateTime,locType)
--              result：number类型
--                      0表示成功
--                      1表示网络环境尚未就绪
--                      2表示连接服务器失败
--                      3表示发送数据失败
--                      4表示接收服务器应答超时
--                      5表示服务器返回查询失败
--                      6表示socket已满，创建socket失败
--                      为0时，后面的5个参数才有意义
--              lat：string类型或者nil，纬度，整数部分3位，小数部分7位，例如"031.2425864"
--              lng：string类型或者nil，经度，整数部分3位，小数部分7位，例如"121.4736522"
--              addr：无意义，保留使用
--              dateTime：无意义，保留使用
--              locType：string类型，位置类型，"LBS"表示基站定位位置，"WIFI"表示WIFI定位位置
-- @bool[opt=nil] reqAddr，此参数无意义，保留
-- @number[opt=20000] timeout，请求超时时间，单位毫秒，默认20000毫秒
-- @string[opt=nil] productKey，IOT网站上的产品证书，此参数可选，用户如果在main.lua中定义了PRODUCT_KEY变量，就不需要传入此参数
-- @string[opt=nil] host，服务器域名，此参数可选，目前仅lib中agps.lua使用此参数。用户脚本中不需要传入此参数
-- @string[opt=nil] port，服务器端口，此参数可选，目前仅lib中agps.lua使用此参数。用户脚本中不需要传入此参数
-- @bool[opt=nil] reqTime，是否需要服务器返回时间信息，true返回，false或者nil不返回，此参数可选，目前仅lib中agps.lua使用此参数。用户脚本中不需要传入此参数
-- @table[opt=nil] reqWifi，搜索到的WIFI热点信息(MAC地址和信号强度)，如果传入了此参数，后台会查询WIFI热点对应的经纬度，此参数格式如下：
--              {
--                  ["1a:fe:34:9e:a1:77"] = -63,
--                  ["8c:be:be:2d:cd:e9"] = -81,
--                  ["20:4e:7f:82:c2:c4"] = -70,
--              }
-- @return string,string, 在任务中使用，返经度，纬度的字符串
-- @usage local lng,lat = lbsLoc.request() -- 仅支持任务中使用
-- @usage lbsLoc.request(cbFnc)
-- @usage lbsLoc.request(cbFnc,true)
-- @usage lbsLoc.request(cbFnc,nil,20000)
function request(cbFnc, reqAddr, timeout, productKey, host, port, reqTime, reqWifi)
    productKey = productKey or _G.PRODUCT_KEY or "v32xEAKsGTIEQxtqgwCldp5aPlcnPs3K"
    sys.taskInit(taskClient, cbFnc, reqAddr, timeout or 20000, productKey or _G.PRODUCT_KEY, host or "bs.openluat.com", port or "12411", reqTime, reqWifi)
    -- if coroutine.running() then
    --     local res, lng, lat = sys.waitUntil("LBSLOC_RESULT")
    --     return res and lng, res and lat
    -- end
end
